1 /**
2  * Author: KonstantIMP <mihedovkos@gmail.com>
3  * Date: Jun 26 2021
4  */
5 module linker.loader;
6 
7 import linker.exception;
8 import std.string : toStringz, fromStringz;
9 
10 /** Extern function for setting path for dll loading */
11 extern(Windows)	{
12     int SetDllDirectoryA(const(char)* path);
13 }
14 
15 /** 
16  * Class for runtime libraries loading
17  */
18 public class Linker {
19     /** Contain lib handler by name */
20     private static void * [string] loadedLibs;
21     /** Contain failed symbols loads by lib */
22     private static string[][string] failedLoads;
23 
24     /** 
25      * Function for catching unsopported symbols
26      */
27     extern(C) static void unsupportedSymbol() {
28         throw new LinkException("The function you are calling is not pressent in your version of lib");
29     }
30 
31     /** 
32      * Link the provided symbol
33      * Params:
34      *   func = The function we are linking
35      *   symbol = Symbol for linking
36      *   libs = Libraries for symbol searching
37      */
38     public static void link(T)(ref T func, string symbol, const string [] libs ...) {
39         func = cast(T)getSymbol(symbol, libs);
40     }
41 
42     /** 
43      * Get the symbol from the lib
44      * Params:
45      *   symbol = Symbol for import
46      *   libs = Libraries for symbol searching
47      * Returns: Handle for the symbol
48      */
49     public static void * getSymbol(string symbol, const string [] libs ...) {
50         void * handle = null;
51 
52         foreach (lib; libs) {
53             if (lib !in loadedLibs) loadLibrary(lib);
54 
55             handle = getSymbolOS(loadedLibs[lib], symbol);
56 
57             if (handle !is null) break;
58         }
59 
60         if (handle is null) {
61             foreach (lib; libs) {
62                 failedLoads[lib] ~= symbol;
63             }
64             handle = &unsupportedSymbol;
65         }
66 
67         return handle;
68     }
69 
70     /** 
71      * Load a dynamic lib
72      * Params:
73      *   library = Library for loading
74      */
75     public static void loadLibrary(string library) {
76         import std.algorithm.searching : canFind;
77         import std.string : split;
78 
79         void * handle = null;
80 
81         if (library.canFind(';')) {
82             foreach (lib; library.split(';')) {
83                 handle = loadLibraryOS(lib);
84                 if (handle !is null) break;
85             }
86         }
87         else handle = loadLibraryOS(library);
88 
89         if (handle is null) throw new LinkException("Library load failed ("~ library ~"): "~ getLastErrorMessageOS());
90     
91         loadedLibs[library] = handle;
92     }
93 
94     /** 
95      * Unload a library
96      * Params:
97      *   library = Library for unloading
98      */
99     public static void unloadLibrary(string library) {
100         unloadLibraryOS(loadedLibs[library]);
101         loadedLibs.remove(library);
102     }
103 
104     /** 
105      * Check load state
106      * Returns: True if was load fails
107      */
108     public static bool isFails() {
109         return failedLoads.length != 0;
110     }
111 
112     /** 
113      * Getter for loaded libs
114      * Returns: Loaded libs list
115      */
116     public static string [] getLoaded() {
117         return loadedLibs.keys;
118     }
119 
120     /** 
121      * Check for lib loading
122      * Params:
123      *   lib = Lib for checking
124      * Returns: true if the lib was loaded
125      */
126     public static bool isLoaded(string lib) {
127         if(lib in loadedLibs) return true;
128         return false;
129     }
130 
131     /** 
132      * Check failed library loads
133      * Params:
134      *   lib = Lib for checking
135      * Returns: Failed loads for the library
136      */
137     public static string [] getFails(string lib) {
138         if (lib in failedLoads) return failedLoads[lib];
139         return null;
140     }
141 
142     /**
143      * Unload all loaded libs at exit
144      */
145     static ~this() {
146         foreach(lib; loadedLibs.keys) {
147             unloadLibrary(lib);
148         }
149     }
150 
151     /** Functions for platform specific library load */
152     version (Windows) {
153         /** Specific imports */
154         import core.sys.windows.winnt : IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_I386;
155         import core.sys.windows.winbase : LoadLibraryA, GetProcAddress, FreeLibrary;
156 
157         /** 
158          * Load library by using OS-specific functions
159          * Params:
160          *   library = Library's for loading name
161          * Returns: Loaded library if everything ok or null in other cases
162          */
163         private static void * loadLibraryOS(string library) {
164             setDLLPath();
165             return LoadLibraryA(cast(char *)toStringz(library));
166         }
167 
168         /** 
169          * Get symbol from library by using OS-specific functions
170          * Params:
171          *   handle = A handle to the DLL module that contains the function or variable
172          *   symbol = The function or variable name, or the function's ordinal value
173          * Returns: Address of symbol or null
174          */
175         private static void * getSymbolOS(void * handle, string symbol) {
176             return GetProcAddress(handle, cast(char *)toStringz(symbol));
177         }
178 
179         /** 
180          * Unload library by using OS-specific functions
181          * Params:
182          *   lib = Library for unloading
183          * Returns: True if everything is ok
184          */
185         private static bool unloadLibraryOS(void * lib) {
186             return cast(bool)FreeLibrary(lib);
187         }
188 
189         /** 
190          * Get the last error message by using OS-specific functions
191          * Returns: String with error message or nothing
192          */
193         private static string getLastErrorMessageOS() {
194             import core.sys.windows.winbase : GetLastError, FormatMessageA, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_ARGUMENT_ARRAY;
195             import core.sys.windows.winnt : LANG_NEUTRAL;
196             import core.stdc.string : memset;
197 
198             char [] buffer = new char[2048];
199             memset(buffer.ptr, '\0', 2048);
200         
201             FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
202                 null, GetLastError(), LANG_NEUTRAL, buffer.ptr, 2048, cast(char **)["\0".ptr].ptr);
203 
204             return fromStringz(buffer.ptr).idup;
205         }
206 
207         /** 
208          * Say Windows where it should search dll
209          */
210         private static void setDLLPath() {
211             static bool is_set = false;
212 
213             if (is_set) return;
214 
215             string gtk_path = findGtkLibs();
216 
217             if (gtk_path !is null) {
218                 SetDllDirectoryA((gtk_path ~ '\0').ptr);
219             }
220 
221             is_set = true;
222         }
223 
224         /** 
225          * Find Gtk libs on the computer
226          * Returns: Path to the Gtk libs or null
227          */
228         private static string findGtkLibs() {
229             import std.algorithm.iteration : splitter;
230             import std.process : environment;
231             import std.path : buildNormalizedPath;
232             import std.file : exists;
233 
234             foreach (lib; [`libgtk-3-0.dll`, `libgtk-4-1.dll`, `gtk-3.dll`, `gtk-4.dll`]) {
235                 foreach (path; splitter(environment.get("PATH"), ';')) {
236                     string dll_path = buildNormalizedPath(path, lib);   
237                 
238                     if (exists(dll_path) == false) continue;
239                     if (checkDLLArch(dll_path)) return path;
240                 }
241             }
242 
243             return null;
244         }
245 
246         /** 
247          * Check a DLL for compatible with the machine
248          * Params:
249          *   dll_path = Path to the dll for check
250          * Returns: true if the dll is compatible with the machine
251          */
252         private static bool checkDLLArch(string dll_path) {
253             import std.stdio : File;
254 
255             File dll = File(dll_path);
256 
257             dll.seek(0xc3);
258             dll.seek(cast(int)dll.rawRead(new int[1])[0]);
259 
260             if (cast(uint)dll.rawRead(new uint[1])[0] != 0x00004550) return false;
261 
262             ushort win_type = cast(ushort)dll.rawRead(new ushort[1])[0];
263 
264             version(Win32) {
265                 return win_type == IMAGE_FILE_MACHINE_I386;
266             }
267             version(Win64) {
268                 return win_type == IMAGE_FILE_MACHINE_AMD64;
269             }
270         }
271     } 
272     else {
273         /** Import OS-specific libs */
274         import core.sys.posix.dlfcn : dlopen, dlerror, dlsym, dlclose;
275         import core.sys.posix.dlfcn : RTLD_NOW, RTLD_GLOBAL;
276 	    
277         import std.path : buildPath;
278 
279         /** String for containing last error message */
280         private static string last_error = null;
281 
282         version(OSX) {
283             /** 
284              * Find the path to the libs on MacOS
285              * Returns: Path to the libs in the MacOS
286              */
287             private static string getBasePath() {
288                 import std.process : environment;
289                 import std.path : buildPath;
290 
291                 static string path = null;
292 
293                 if (path !is null) return path;
294 
295                 path = environment.get("GTK_BASEPATH");
296                 if (path is null) {
297                     path = environment.get("HOMEBREW_ROOT");
298                     if (path !is null) path = buildPath(path, "lib");
299                 }
300                 
301                 return path;
302             }
303 
304             private static string base_path = getBasePath();
305         }
306         else {
307             private static string base_path = "";
308         }
309 
310         /** 
311          * Load library by using OS-specific functions
312          * Params:
313          *   library = Library's for loading name
314          * Returns: Loaded library if everything ok or null in other cases
315          */
316         private static void * loadLibraryOS(string library) {
317             import std.path : buildPath;
318 
319             void * handle = dlopen(cast(char *)toStringz(buildPath(base_path, library)), RTLD_GLOBAL | RTLD_NOW);
320         
321             if (!handle) last_error = fromStringz(dlerror()).idup;
322 
323             return handle;
324         }
325 
326         /** 
327          * Get symbol from library by using OS-specific functions
328          * Params:
329          *   handle = A handle to the DLL module that contains the function or variable
330          *   symbol = The function or variable name, or the function's ordinal value
331          * Returns: Address of symbol or null
332          */
333         private static void * getSymbolOS(void * handle, string symbol) {
334             void * symbol_handle = dlsym(handle, cast(char *)toStringz(symbol));
335             
336             if (!symbol_handle) last_error = fromStringz(dlerror()).idup;
337 
338             return symbol_handle;
339         }
340 
341         /** 
342          * Unload library by using OS-specific functions
343          * Params:
344          *   lib = Library for unloading
345          * Returns: True if everything is ok
346          */
347         private static bool unloadLibraryOS(void * lib) {
348             int res = dlclose(lib);
349             if (res != 0) last_error = fromStringz(dlerror()).idup;
350             return res == 0;
351         }
352 
353         /** 
354          * Get the last error message by using OS-specific functions
355          * Returns: String with error message or nothing
356          */
357         private static string getLastErrorMessageOS() {
358             scope(exit) last_error = null;
359             return last_error;
360         }
361     }
362 }